/* collection:BlueStraggler         http://blue.straggler.org
 * codebase:Scart
 * toolset:Scart
 * module:Core
 * package:org.stellabs.scart
 * file:org/stellabs/scart/package.scala
 * 
 * Copyright 2012, 2013 Stellabs.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.stellabs.scart


// CAUTION! THIS IS EXPERIMENTAL, AT LEAST AS MUCH AS SCALA MACROS

import language.experimental.macros
import scala.reflect.macros.Context

/*
 *  KNOWN LIMITATIONS:
 *  - if an inner function is defined in an outer function, an inclosure from within the inner will refer to the outer
 */

package object tracing {

  // First Entry points
  def @@--()= NeverTracer  // disable a trace directly in the code
  def @@++()= CheckTracer  // enable, ditto 
  
  // Settings Entry point
  protected def ?()= org.stellabs.scart.config.Settings;  // A Shortcut to the settings


  /*
   * TRACE COMMANDS: MACRO DECLARATIONS
   */
  sealed trait Tracer{
    def @@():Unit                 = ???;   // Fast trace method without entry (to collect other information)
    def @@#(entry:String):Unit    = ???;   // Fast trace method of literal strings only
    def $$(entry:String):Unit 		= ???;   // Trace method of evaluated strings
    def %(priority:Int):Tracer 		= ???;   // Priority filter, related to settings
    def %(cond:Boolean):Tracer 		= ???;   // Basic boolean literal filter
  }

  // A trace entry-point that traces, or not, depending on the settings
  object CheckTracer extends Tracer{
    override def @@():Unit              = macro `fasttrace():Unit`;
    override def @@#(entry:String):Unit = macro `fasttrace(String):Unit`;
    override def $$(entry:String):Unit  = macro `trace(String):Unit`;
    override def %(priority:Int):Tracer = macro `priotrace(Int):Tracer`;
    override def %(cond:Boolean):Tracer = macro `priotrace(Boolean):Tracer`;
  }

  // A trace entry-point that ignores the trace requests; aka "do-nothing"
  object NeverTracer extends Tracer{
    override def @@():Unit              = macro `donttrace():Unit`;
    override def @@#(s:String):Unit     = macro `donttrace(String):Unit`;
    override def $$(s:String):Unit      = macro `donttrace(String):Unit`;  
    override def %(priority:Int):Tracer = macro `nevertracer(Int):Tracer`;
    override def %(cond:Boolean):Tracer = macro `nevertracer(Boolean):Tracer`;
   }

  
  /*
   * TRACE COMMANDS: MACRO IMPLEMENTATIONS
   */

  // Redirect to the "do-nothing" tracer if and only if the condition is false
	def `priotrace(Boolean):Tracer` (c:Context)(cond:c.Expr[Boolean]):c.Expr[Tracer]=try{
	  import c.universe._;
	  val Literal(Constant(_cond:Boolean)) = cond.tree;
	  if (_cond) reify{ CheckTracer } else reify{ NeverTracer} // ret
	}
	catch{ case _:Throwable => abort(c, "invalid Condition", "possible cause: mistakenly using a non-literal Boolean"); }
	
  // Redirect to the "do-nothing" tracer if a trace's priority Int is bigger than specified in the settings
	def `priotrace(Int):Tracer` (c:Context)(priority:c.Expr[Int]):c.Expr[Tracer]=try{
		import c.universe._;
		val Literal(Constant(_priority:Int)) = priority.tree;
		if (?.priority >= _priority) reify{ CheckTracer } else reify{ NeverTracer } // ret
	}
	catch{ case _:Throwable => abort(c, "invalid Priority", "possible cause: mistakenly using a non-literal Int"); }

  	
  // Ignore any trace request, evaluate to ()
  def `donttrace():Unit`(c:Context)():c.Expr[Unit] = { c.universe.reify{ () } }
  def `donttrace(String):Unit`(c:Context)(s:c.Expr[String]):c.Expr[Unit] = { c.universe.reify{ () } }

  def `nevertracer(Int):Tracer` (c:Context)(priority:c.Expr[Int]):c.Expr[Tracer]={c.universe.reify{ NeverTracer } }
  def `nevertracer(Boolean):Tracer` (c:Context)(cond:c.Expr[Boolean]):c.Expr[Tracer]={c.universe.reify{ NeverTracer } }


  // Gather the additional data that may be useful for the trace, invoke the formatter then the printer
  def `trace(String):Unit` (c:Context)(entry:c.Expr[String]):c.Expr[Unit]={
	  import c.universe._;
	  if (?.doIt){
		  val data          = Data(?.escape, _currentInclosure(c), _currentLocation(c) );
		  val `(ue)output`  = c.Expr[String]	(Literal(Constant( ?.formatter(data) )));
		  c.Expr(reify{ (?.escapePrinter)( `(ue)output`.splice, entry.splice ) }.tree)
	  } 
	  else reify({})
  }

  // Ditto but for literal strings; save some steps and precious ticks
  def `fasttrace(String):Unit` (c:Context)(entry:c.Expr[String]):c.Expr[Unit]={
    import c.universe._;
    if (?.doIt){
      val _entry = try{ val Literal(Constant(e:String)) = entry.tree; e
      } catch{ case _:Throwable => abort(c, "invalid Entry", "possible cause: mistakenly using a non-literal String");}
      val data          = Data( _entry, _currentInclosure(c), _currentLocation(c) );
      val `(ue)output`  = c.Expr[String]( Literal(Constant( ?.formatter(data) )));
      c.Expr(reify{(?.printer)( `(ue)output`.splice )}.tree)
    } 
    else reify({})
  }

  // Ditto but without a string entry
  def `fasttrace():Unit` (c:Context)():c.Expr[Unit]={
    import c.universe._;
    if (?.doIt){
      val data          = Data( null, _currentInclosure(c), _currentLocation(c) );
      val `(ue)output`  = c.Expr[String]( Literal(Constant( ?.formatter(data) )));
      c.Expr(reify{(?.printer)( `(ue)output`.splice )}.tree)
    } 
    else reify({})
  }

  
  /*
   * TRACE DATA
   */

  // Taxonomy of the current method, class, as specific as possible
  object InclosureKind extends Enumeration { val UNKNOWN, CLASS, METHOD = Value }
  case class Inclosure(val kind:InclosureKind.Value, val id:String){
    def this(v:Int,s:String) = this( InclosureKind(v), s );
  }
  
  // Location in the source code
  case class Location(source:String, val line:Int, val column:Int){
    def this(t:(String,Int,Int)) = this( if(null==t) null else t._1, if(null==t) 0 else t._2, if(null==t) 0 else t._3 );
  }
  
  // Combination of the above, with the trace's entry string
  case class Data(val entry:String, val member:Inclosure, val loc:Location);

  
  /*
   * FEATURES USED IN THE ABOVE, BUT CAN ALSO BE USEFUL ELSEWHERE
   */

  // Determine the position in the source code: Filename, Line, Column
  def $currentPosition:(String,Int,Int) = macro `currentPosition:(String,Int,Int)`;
  def `currentPosition:(String,Int,Int)` (c:Context):c.Expr[(String,Int,Int)]={
    import c.universe._;
    val pos = _currentPosition(c);
    if (null==pos) reify{(null,0,0)} else {
      val `(ue)path`    = c.Expr[String]  (Literal(Constant( pos._1 )));
      val `(ue)line`    = c.Expr[Int]     (Literal(Constant( pos._2 )));
      val `(ue)column`  = c.Expr[String]  (Literal(Constant( pos._3 )));
      c.Expr[(String,Int,Int)]( reify{(`(ue)path`.splice,`(ue)line`.splice, `(ue)column`.splice)}.tree ) // returned
    }
  }
  def _currentPosition(c:Context) = if (null==c.enclosingPosition || !c.enclosingPosition.isDefined) null else
    ( c.enclosingPosition.source.path, c.enclosingPosition.line, c.enclosingPosition.column );

  
  // Determine the location in the source code (similar to position): Filename, Line, Column
  def $currentLocation:tracing.Location = macro `currentLocation:Location`;
  def `currentLocation:Location` (c:Context):c.Expr[tracing.Location]= c.universe.reify{
    new Location( `currentPosition:(String,Int,Int)`(c).splice )
  }
  def _currentLocation(c:Context) = new Location(_currentPosition(c));


  // Identify the class or method within which the source code being executed is.
  def $currentInclosure:tracing.Inclosure = macro `currentInclosure:Inclosure`;
  def `currentInclosure:Inclosure` (c:Context):c.Expr[tracing.Inclosure]={
    import c.universe._;
    val Inclosure(kind,id) = _currentInclosure(c:Context);
    reify{new Inclosure( c.Expr[Int](Literal(Constant(kind.id))).splice, c.Expr[String](Literal(Constant(id))).splice )}
  }
  def _currentInclosure(c:Context) = 
    if (null != c.enclosingMethod && !c.enclosingMethod.isEmpty) 
      Inclosure( InclosureKind.METHOD, c.enclosingMethod.symbol.fullName )
    else if (null != c.enclosingClass && !c.enclosingClass.isEmpty) 
      Inclosure( InclosureKind.CLASS, c.enclosingClass.symbol.fullName )
    else Inclosure( InclosureKind.UNKNOWN , "<???>" );
  
  
  /*
   * FEATURES UNUSED IN THE ABOVE, BUT THAT ALSO BE USEFUL ELSEWHERE
   */
  
  // Not as flexible as reflection, but many times faster when that is applicable: class name     
  def $currentClassName:String = macro `currentClassName:String`;
  def `currentClassName:String` (c:Context):c.Expr[String]={
    import c.universe._;
    c.Expr(Literal(Constant(c.enclosingClass.symbol.fullName)))
  }

  // Not as flexible as reflection, but many times faster when that is applicable: method name
  // Limitation: it doesn't give an outer method's inner method name
  def $currentMethodName:String = macro `currentMethodName:String`;
  def `currentMethodName:String` (c:Context):c.Expr[String]={
    import c.universe._;
    c.Expr(Literal(Constant(c.enclosingMethod.symbol.fullName)))
  }

 
  /*
   * SETTINGS
   */

  // Base class for a Settings singleton object
  final object SettingsBase{
  
    // Values for main switch
    val `ON`  = true;
    val `OFF` = false;

    // Priority levels, thresholds
    val HIGHEST_PRIORITY = 1;
    val LEAST_PRIORITY   = Int.MaxValue;
    
    // The format method of choice
    type FormatterType = Data => String;

    // The printing method of choice
    type PrinterType  = String => Unit;

  }
  trait SettingsBase{
    import SettingsBase._
    
	  // Must be defined in the Settings implementation
	  protected[this] def trace:Boolean; // Enable/disable the traces
    protected[this] def priority:Int;  // If traces enabled, designate the priority threshold of handled traces

	  // The format method of choice
	  def  formatter: FormatterType;
	  
	  // The printing method of choice
	  def  printer: PrinterType;

	  // A deviation from normal printing, to finally resolve a non-literal entry string before invoking the printer
	  val escape = 0.toChar.toString;
	  def escapePrinter(fstr:String, entry:String) = printer(fstr.replace(escape, entry));

	  // The ultimate decision that allows or forbids the trace. A finer-grained control may be reached by overriding it
	  def doIt = trace;


	  // Available printing methods (in progress)
	  object Printer {
	    val `System.out.println`:PrinterType = s => {System.out.println(s); System.out.flush}
	    val `System.err.println`:PrinterType = s => {System.err.println(s); System.err.flush}
	  }
    
	  // Available formatting methods
	  object Formatter {
      
	    // FOR TESTS
	    val `nihilistic`:FormatterType = d => null;                    // To eliminate traces, switch to `OFF` instead!!
	    val `smoker`:FormatterType     = d => "This is a smoke test."  // Won't fail if the entry is problematical
	    
	    // No more, no less than the entry (except '\n')
	    val `as-is`:FormatterType = {d => val Data(entry,_,_)=d; entry} 
	    // Only the inclosure identification
      val `inclosure-only`:FormatterType = { d =>
        val Data(_,Inclosure(k,iname),_)=d;
        s"${k.toString.toLowerCase} ${iname}" }
      // All
	    val `all`:FormatterType = { d =>
	      val Data(entry,Inclosure(k,iname),Location(path,line,col))=d;
	      s"${path}(L${line},C${col}): ${entry} [${k.toString.toLowerCase} ${iname}]"
	    }

	  }
		def +={ ??? }
		def -={ ??? }

	}

//    ADD: Inclosure, path reduction, etc
  
  
  // Abort macro evaluation and provide details the cause
  private def abort(c:Context, desc:String, hint:String=null) =
    c.abort(c.enclosingPosition, "Trace Macro Error: " + (if (null==hint) desc else s"${desc} (${hint})"));
  
} // package object tracing

